package org.nvframe.manager;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.newdawn.slick.AngelCodeFont;
import org.newdawn.slick.Font;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.Sound;
import org.newdawn.slick.loading.LoadingList;
import org.newdawn.slick.util.ResourceLoader;
import org.nvframe.exception.NVFrameException;
import org.nvframe.util.AnimationResource;
import org.nvframe.util.ParticleResource;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * 
 * @author Nik Van Looy
 */
public class ResourceManager {

	private static ResourceManager _instance = new ResourceManager();

	public final static ResourceManager getInstance() {
		return _instance;
	}
	
	private Map<String, Sound> sounds;
	private Map<String, Image> images;
	private Map<String, Font> fonts;
	private Map<String, AnimationResource> animations;
	private Map<String, ParticleResource> particles;

	private ResourceManager() {
		sounds = new HashMap<String, Sound>();
		images = new HashMap<String, Image>();
		animations = new HashMap<String, AnimationResource> ();
		fonts = new HashMap<String, Font>();
		particles = new HashMap<String, ParticleResource> ();
	}
	
	/**
	 * 
	 * @param location
	 * @param deferred
	 * @throws NVFrameException
	 */
	public void loadResources(String location, boolean deferred) throws NVFrameException {
		InputStream inRes = ResourceLoader.getResourceAsStream(location);
		
		try {
			loadResources(inRes, deferred);
		} catch (NumberFormatException e) {
			throw new NVFrameException("could not load resource: " + location, e);
		} catch (DOMException e) {
			throw new NVFrameException("could not load resource: " + location, e);
		} catch (NVFrameException e) {
			throw new NVFrameException("could not load resource: " + location, e);
		}
	}
	
	/**
	 * Iterate over resource tags in a resource xml file
	 * and loads the resource in the appropriate Map
	 * 
	 * @param is
	 * @param deferred
	 * @throws DOMException 
	 * @throws NumberFormatException 
	 * @throws NVFrameException
	 */
	public void loadResources(InputStream is, boolean deferred) throws NumberFormatException, DOMException, NVFrameException {
		DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder docBuilder = null;
		
		try {
			docBuilder = docBuilderFactory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			throw new NVFrameException("Could not load resources", e);
		}
		
		Document doc = null;
		
		try {
			doc = docBuilder.parse(is);
		} catch (SAXException e) {
			throw new NVFrameException("Could not load resources", e);
		} catch (IOException e) {
			throw new NVFrameException("Could not load resources", e);
		}

		doc.getDocumentElement().normalize();

		// load the root tag
		NodeList listResources = doc.getElementsByTagName("resource");

		int totalResources = listResources.getLength();

		if (deferred)
			LoadingList.setDeferredLoading(true);

		for (int resourceIdx = 0; resourceIdx < totalResources; resourceIdx++) {

			Node resourceNode = listResources.item(resourceIdx);

			if (resourceNode.getNodeType() == Node.ELEMENT_NODE) {
				Element resourceElement = (Element) resourceNode;

				String type = resourceElement.getAttribute("type");

				// check the resource type and call appropreate method
				if (type.equals("image")) {
					loadImage(resourceElement.getAttribute("id"), resourceElement.getTextContent());
				} 
				else if (type.equals("animation")) {
					loadAnimation(resourceElement.getAttribute("id"), 
							resourceElement.getTextContent(),
							Integer.parseInt(resourceElement.getAttribute("tileWidth")),
							Integer.parseInt(resourceElement.getAttribute("tileHeight")),
							Integer.parseInt(resourceElement.getAttribute("duration")));
				} 
				else if (type.equals("sound")) {
					loadSound(resourceElement.getAttribute("id"), resourceElement.getTextContent());
				} 
				else if (type.equals("font")) {
					loadFont(resourceElement.getAttribute("id"), 
							resourceElement.getTextContent(),
							resourceElement.getAttribute("image"));
				}
				else if (type.equals("particlesystem")) {
					loadParticleSystem(resourceElement.getAttribute("id"), 
							resourceElement.getAttribute("system"),
							resourceElement.getTextContent());
				}
			}
		}
	}

	/**
	 * Loads an Image resource from a given path
	 * 
	 * @param id The resource id (declared in xml with id attribute)
	 * @param path The path to the image file
	 * @throws NVFrameException
	 */
	private void loadImage(String id, String path) throws NVFrameException {
		if (path == null || path.length() == 0)
			throw new NVFrameException("Image resource [" + id + "] has invalid path");

		try {
			Image image = new Image(path);
			images.put(id, image);
		} catch (SlickException e) {
			throw new NVFrameException("Could not load image [id: " + id + " | " +
					"path: " + path + "]", e);
		}

	}
	
	/**
	 * Loads an Animation resource from a given path
	 * 
	 * @param id The resource id (declared in xml with id attribute)
	 * @param path The path to the sprite file
	 * @param tileWidth The width of a tile
	 * @param tileHeight The height of a tile
	 * @param duration The duration in milliseconds (can be overridden by components)
	 * @throws NVFrameException
	 */
	private void loadAnimation(String id, String path, int tileWidth, 
			int tileHeight, int duration) throws NVFrameException {
		if (path == null || path.length() == 0)
			throw new NVFrameException("Image resource [" + id + "] has invalid path");

		try {
			Image image = new Image(path);
			AnimationResource animation = new AnimationResource(image, tileWidth, tileHeight, duration);
			animations.put(id, animation);
		} catch (SlickException e) {
			throw new NVFrameException("Could not load animationsprite [id: " + id + " | " +
					"path: " + path + "]", e);
		}
	}

	/**
	 * Loads a Sound resource from a given path
	 *  
	 * @param id The resource id (declared in xml with id attribute)
	 * @param path The path to the sound file
	 * @throws NVFrameException
	 */
	private void loadSound(String id, String path) throws NVFrameException {
		if (path == null || path.length() == 0)
			throw new NVFrameException("Sound resource [" + id + "] | path can't be null");

		try {
			Sound sound = new Sound(path);
			sounds.put(id, sound);
			SoundManager.getInstance().addSound(id, sound);
		} catch (SlickException e) {
			throw new NVFrameException("Could not load sound [id: " + id + " | " +
					"path: " + path + "]", e);
		}
	}

	/**
	 * Loads an Font resource from a given path
	 * 
	 * @param id The resource id (declared in xml with id attribute)
	 * @param fontPath The path to the .fnt file
	 * @param imagePath The path to the image file
	 * @throws NVFrameException
	 */
	private void loadFont(String id, String fontPath, String imagePath) throws NVFrameException {
		if (fontPath == null)
			throw new NVFrameException("Font resource [" + id + "] | fontPath can't be null");

		if (imagePath == null)
			throw new NVFrameException("Font resource [" + id + "] | imagePath can't be null");

		try {
			Font font = new AngelCodeFont(fontPath, imagePath);
			fonts.put(id, font);
		} catch (SlickException e) {
			throw new NVFrameException("Could not load font [id: " + id + " | " +
					"fontPath: " + fontPath + " | imagePath " + imagePath + "]", e);
		}
	}
	
	/**
	 * Creates a new ParticleSystem
	 * 
	 * @param id The resource id
	 * @param xmlPath The path to the Particle XML settings (created by Pedigree)
	 * @param imagePath The path to the Image file used by the ParticleSystem
	 * @throws NVFrameException
	 */
	private void loadParticleSystem(String id, String systemCfg, String image) 
			throws NVFrameException {
		
		if (systemCfg == null || systemCfg.isEmpty())
			throw new NVFrameException("ParticleSystem resource [" + id + "] | xmlPath can't be null");

		ParticleResource particleResource;
		
		if (image == null || image.isEmpty())
			particleResource = new ParticleResource(systemCfg, "img/particle.tga");
		else
			particleResource = new ParticleResource(systemCfg, image);

		particles.put(id, particleResource);
	}
	
	/**
	 * Check if a specified Image resource exists
	 * 
	 * @param id The resource id
	 * @return True if exists False otherwise
	 */
	public boolean containsImageResource(String id) {
		return images.containsKey(id);
	}
	
	/**
	 * Check if a specified Animation resource exists
	 * 
	 * @param id The resource id
	 * @return True if exists False otherwise
	 */
	public boolean containsAnimationResource(String id) {
		return animations.containsKey(id);
	}
	
	/**
	 * Check if a specified Sound resource exists
	 * 
	 * @param id The resource id
	 * @return True if exists False otherwise
	 */
	public boolean containsSoundResource(String id) {
		return sounds.containsKey(id);
	}
	
	/**
	 * Check if a specified Font resource exists
	 * 
	 * @param id The resource id
	 * @return True if exists False otherwise
	 */
	public boolean containsFontResource(String id) {
		return fonts.containsKey(id);
	}
	
	/**
	 * Check if a specified ParticleResource exists
	 * 
	 * @param id The resource id
	 * @return True if exists False otherwise
	 */
	public boolean containsParticleSystemResource(String id) {
		return particles.containsKey(id);
	}

	/**
	 * Get an Image resource by id
	 * 
	 * @param id The resource id
	 * @return The Image resource
	 * @throws NVFrameException
	 */
	public Image getImage(String id) throws NVFrameException {
		if(!images.containsKey(id))
			throw new NVFrameException("Image resource [id: " + id + "] not found");
		return images.get(id).copy();
	}
	
	/**
	 * Get a Sound resource by id
	 * 
	 * @param id The resource id
	 * @return The Sound resource
	 * @throws NVFrameException 
	 */
	public Sound getSound(String id) throws NVFrameException {
		if(!sounds.containsKey(id))
			throw new NVFrameException("Sound resource [id: " + id + "] not found");
		return sounds.get(id);
	}

	/**
	 * Get a Font resource by id
	 * 
	 * @param id The resource id
	 * @return The Font resource
	 * @throws NVFrameException 
	 */
	public Font getFont(String id) throws NVFrameException {
		if(!fonts.containsKey(id))
			throw new NVFrameException("Font resource [id: " + id + "] not found");
		return fonts.get(id);
	}

	/**
	 * Get an AnimationResource by id
	 * 
	 * @param id The resource id
	 * @return The AnimationResource
	 * @throws NVFrameException
	 */
	public AnimationResource getAnimationResource(String id) throws NVFrameException {
		if(!animations.containsKey(id))
			throw new NVFrameException("AnimationResource [id: " + id + "] not found");
		return animations.get(id);
	}

	/**
	 * Get an ParticleResource by id
	 * 
	 * @param id The resource id
	 * @return The ParticleResource
	 * @throws NVFrameException
	 */
	public ParticleResource getParticleResource(String id) throws NVFrameException {
		if(!particles.containsKey(id))
			throw new NVFrameException("ParticleResource [id: " + id + "] not found");
		return particles.get(id);
	}

}
